﻿using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Runtime.CompilerServices;
using System.Threading;

namespace System
{
    using Function = Func<double, double>;

    public static class FuncExtentions
    {
        //private static readonly double[,] diff_a = new double[,]
        //                                           {
        //                                               {-1, 1, 0, 0, 0, 0},
        //                                               {-3, 4, -1, 0, 0, 0},
        //                                               {-11, 18, -9, 2, 0, 0},
        //                                               {-25, 48, -36, 16, -3, 0},
        //                                               {-137, 300, -300, 200, -75, 12}
        //                                           };

        //private static readonly double[] diff_b = new double[] { 1, 2, 6, 12, 60 };

        public static Function Add(this Function f, double a)
        {
            return Math.Abs(a) < double.Epsilon ? f : x => f(x) + a;
        }

        public static Function Add(this Function f1, Function f2)
        {
            return x => f1(x) + f2(x);
        }

        public static Function ArgumentReverse(this Function f)
        {
            return x => f(-x);
        }

        public static Function ArgumentShift(this Function f, double x0)
        {
            return Math.Abs(x0) < double.Epsilon ? f : x => f(x - x0);
        }

        public static Function Divade(this Function f, double a)
        {
            return Math.Abs(a - 1) < double.Epsilon ? f : x => f(x) / a;
        }

        public static Function Divade(this Function f1, Function f2) { return x => f1(x) / f2(x); }

        /// <summary>Функция от функци q(f(x))</summary>
        /// <param name="f">Внутренняя функция</param>
        /// <param name="q">Внешняя функция</param>
        /// <returns>ФУнкция q от функции f</returns>
        public static Function Func(this Function f, Function q) { return x => q(f(x)); }

        /// <summary>Функция от функции f(q(x))</summary>
        /// <param name="f">Внешняя функция</param>
        /// <param name="q">Внутренняя функция</param>
        /// <returns>Функция f от функции q</returns>
        public static Function FuncFor(this Function f, Function q) { return x => f(q(x)); }

        public static Function GetAKF(this Function f, double deltaX, double dx = 0.0001, double x0 = 0.0)
        {
            return f.GetConvolution(f.ArgumentReverse(), deltaX, dx, x0);
        }

        public static Function GetConvolution(this Function f, Function g, double deltaX, double dx = 0.0001, double x0 = 0.0)
        {
            var lv_Xmin = x0 - deltaX / 2;
            var lv_Xmax = x0 + deltaX / 2;
            return x => ((Function)(t => f(t) * g(x - t))).GetIntegralValue(lv_Xmin, lv_Xmax, dx);
        }

        public static Function GetDifferencial(this Function f, double dx = 0.0001, int n = 0)
        {
            return x => f.GetDifferencialValue(x, dx, n);
        }

        public static double GetDifferencialValue(this Function f, double x, double dx = 0.0001, int n = 0)
        {
            Contract.Requires<ArgumentOutOfRangeException>(n >= 0 && n <= 4, "значение n должно быть в пределах 0 < n < 4");

            //if(n < 0)
            //    throw new ArgumentOutOfRangeException("n", "значение n должно быть в пределах 0 < n < 5");

            if(n == 0)
            {
                var lv_DeltaX = dx / 2.0;
                var lv_Xmin = x - lv_DeltaX;
                var lv_Xmax = x + lv_DeltaX;
                var lv_DeltaY = f(lv_Xmax) - f(lv_Xmin);
                return Math.Abs(dx) > double.Epsilon
                           ? lv_DeltaY / dx
                           : (lv_DeltaY > .0
                                  ? double.PositiveInfinity
                                  : (lv_DeltaY >= .0 ? double.NaN : double.NegativeInfinity));
            }
            n--;
            var lv_Result = .0;
            for(var i = 0; i < 6; i++)
                lv_Result += MathService.DifferencialEquations.Numerical.Solover.Differential.diff_a[n, i] * f(x + (i * dx));
            return (lv_Result / MathService.DifferencialEquations.Numerical.Solover.Differential.diff_b[n]) / dx;
        }

        public static double GetIntegralValue(this Function f, double x1, double x2, double dx = 0.0001)
        {
            var lv_Result = .0;
            var lv_DeltaX = x2 - x1;
            var N = Math.Abs(lv_DeltaX) / dx;
            var x = x1;
            var y = f(x);
            for(var i = 0; i < N; i++)
                lv_Result += y + (y = f(x += dx));
            return .5 * dx * lv_Result;
        }

        public static double GetIntegralValue(this Function f,
                                              Func<Function, double, double> Core, double x1, double x2,
                                              double dx = 0.0001)
        {
            var lv_Result = .0;
            var lv_DeltaX = x2 - x1;
            var N = Math.Abs(lv_DeltaX) / dx;
            var x = x1;
            var lv_Core = Core(f, x);
            for(var i = 0; i < N; i++)
                lv_Result += lv_Core + (lv_Core = Core(f, x += dx));
            return .5 * dx * lv_Result;
        }

        public static double GetIntegralValue(this Function f, Func<double, double, double> Core, double x1,
                                              double x2, double dx = 0.0001)
        {
            var lv_Result = .0;
            var lv_DeltaX = x2 - x1;
            var N = Math.Abs(lv_DeltaX) / dx;
            var x = x1;
            var lv_Core = Core(f(x), x);
            for(var i = 0; i < N; i++)
                lv_Result += lv_Core + (lv_Core = Core(f(x += dx), x));
            return .5 * dx * lv_Result;
        }

        public static Function GetPeriodic(this Function f, double T, double x0 = 0.0)
        {
            if(Math.Abs(x0) > double.Epsilon)
                f = f.ArgumentShift(x0);
            return x => f((x % T) + ((x < 0) ? T : 0));
        }

        public static double GetPower(this Function f, double x1, double x2, double dx = 0.0001)
        {
            return ((Function)(x =>
                         {
                             var num = f(x);
                             return num * num;
                         })).GetIntegralValue(x1, x2, dx) / (x2 - x1);
        }

        public static TResult[] GetValues<TArgument, TResult>(this Func<TArgument, TResult> f, TArgument[] Argument)
        {
            return Argument.Function(f);
        }



        public static Function Inverse(this Function f) { return x => 1 / f(x); }

        public static Function Multiply(this Function f1, Function f2) { return x => f1(x) * f2(x); }

        public static Function Multiply(this Function f, double a)
        {
            return Math.Abs(a - 1) < double.Epsilon ? f : x => (f(x) * a);
        }

        public static Function Power(this Function f, double a)
        {
            return Math.Abs(a - 1) < double.Epsilon ? f : x => Math.Pow(f(x), a);
        }

        public static Function Reverse(this Function f) { return x => -f(x); }

        public static Function SetParameter(this Func<double, double, double> f, double a,
                                                        bool IsFirst = false)
        {
            return IsFirst
                       ? (Function)(x => f(a, x))
                       : x => f(x, a);
        }

        public static Function Substract(this Function f1, Function f2) { return x => f1(x) - f2(x); }

        public static Function Substract(this Function f, double a) { return Math.Abs(a) < double.Epsilon ? f : x => f(x) - a; }

        #region Nested type: FuncCalculator

        private class FuncCalculator<TArgument, TResult>
        {
            public struct func
            {
                public TArgument x;
                public Func<TArgument, TResult> f;
            }

            //public readonly List<KeyValuePair<TArgument, TResult>> Values;
            public readonly List<TArgument> Argument;
            public readonly List<TResult> Result;
            private int f_Complited;
            private int f_Count;

            public FuncCalculator()
            {
                Argument = new List<TArgument>();
                Result = new List<TResult>();
                //Values = new List<KeyValuePair<TArgument, TResult>>();
            }

            public event EventHandler<EventArgs<List<TArgument>, List<TResult>>> OnComplited;

            //[MethodImpl(MethodImplOptions.Synchronized)]
            //public void CalculationComplited(IAsyncResult AsyncResult)
            //{
            //        var lv_AsyncState = (object[])AsyncResult.AsyncState;
            //        var lv_Argument = (TArgument)lv_AsyncState[0];
            //        var lv_Result = ((Func<TArgument, TResult>)lv_AsyncState[1]).EndInvoke(AsyncResult);
            //        Argument.Add(lv_Argument);
            //        Result.Add(lv_Result);
            //        //Values.Add(new KeyValuePair<TArgument, TResult>(lv_Argument, lv_Result));
            //        f_Complited++;
            //        Check();
            //}

            [MethodImpl(MethodImplOptions.Synchronized)]
            public void Calculate(object State)
            {
                var lv_func = (func)State;
                Argument.Add(lv_func.x);
                Result.Add(lv_func.f(lv_func.x));
                f_Complited++;
                Check();
            }

            private void Check()
            {
                if((f_Count == f_Complited) && (OnComplited != null))
                    OnComplited(this, new EventArgs<List<TArgument>, List<TResult>>(Argument, Result));
            }

            [MethodImpl(MethodImplOptions.Synchronized)]
            public void SetCount(int Count)
            {
                f_Count = Count;
                Check();
            }
        }

        ///<summary>Вычислить значения функции параллельно</summary>
        ///<param name="f">Вычисляемая функция</param>
        ///<param name="Arguments">Область определения</param>
        ///<typeparam name="TArgument">Тип аргумента</typeparam>
        ///<typeparam name="TResult">Тип значения</typeparam>
        ///<returns>Массив значений функции</returns>
        public static TResult[] GetValuesParralel<TArgument, TResult>(this Func<TArgument, TResult> f,
                                                                      IEnumerable<TArgument> Arguments)
        {
            Contract.Requires(Arguments != null);
            var reset = new AutoResetEvent(false);
            var calculator = new FuncCalculator<TArgument, TResult>();
            calculator.OnComplited += (Sender, Args) => reset.Set();
            var count = 0;
            Arguments.Foreach(x =>
                                  {
                                      count++;
                                      ThreadPool.QueueUserWorkItem(calculator.Calculate,
                                                                   new FuncCalculator<TArgument, TResult>.func { x = x, f = f });
                                      //f.BeginInvoke(x, calculator.CalculationComplited, new object[] { x, f });
                                  });
            calculator.SetCount(count);
            reset.WaitOne();
            return calculator.Result.ToArray();
        }

        #endregion
    }


}